package org.tim.websocket;

import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonSyntaxException;
import io.netty.channel.EventLoopGroup;
import lombok.extern.slf4j.Slf4j;
import lombok.var;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.tim.common.msg.MsgType;
import org.tim.common.msg.TimMessage;
import org.tim.websocket.chat.ChatKit;
import org.tio.core.ChannelContext;
import org.tio.core.Tio;
import org.tio.http.common.HttpRequest;
import org.tio.http.common.HttpResponse;
import org.tio.websocket.common.WsRequest;
import org.tio.websocket.common.WsResponse;
import org.tio.websocket.server.handler.IWsMsgHandler;

import java.util.Collection;

@Slf4j
public class TimWebSocketHandler implements IWsMsgHandler {
    @Autowired
    private ApplicationContext applicationContext;

    private final EventLoopGroup workPool;
    private final EventLoopGroup heartbeatPool;
    private final Gson gson;

    public TimWebSocketHandler(EventLoopGroup workPool, EventLoopGroup heartbeatPool) {
        this.workPool = workPool;
        this.heartbeatPool = heartbeatPool;

        this.gson = new GsonBuilder().create();
    }

    @Override
    public HttpResponse handshake(HttpRequest httpRequest, HttpResponse httpResponse, ChannelContext channelContext) throws Exception {
        String userId = httpRequest.getParam("userId");

        if (log.isDebugEnabled()) {
            log.debug("User {} online", userId);
        }

        Tio.bindUser(channelContext, userId);

        return httpResponse;
    }

    @Override
    public Object onClose(WsRequest wsRequest, byte[] bytes, ChannelContext channelContext) throws Exception {
        String userId = channelContext.userid;

        if (log.isDebugEnabled()) {
            log.debug("User {} offline", userId);
        }

        Tio.unbindUser(channelContext);

        return null;
    }

    @Override
    public Object onText(WsRequest wsRequest, String text, ChannelContext channelContext) throws Exception {
        try {
            final var timMessage = gson.fromJson(text, TimMessage.class);

            // 心跳应答
            if (MsgType.HEARTBEAT.equals(timMessage.getMsgType())) {
                heartbeatPool.execute(new Runnable() {
                    @Override
                    public void run() {
                        Tio.sendToUser(
                                channelContext.tioConfig,
                                timMessage.getFromUserId(),
                                WsResponse.fromText(gson.toJson(timMessage), "utf-8"));
                    }
                });
                return null;
            }

            for (var chatKit : getChatKits()) {
                if (!chatKit.isSupport(timMessage)) continue;

                send(chatKit, timMessage, wsRequest, channelContext, 2, "");
            }
        } catch (JsonSyntaxException e) {
            if (log.isErrorEnabled()) {
                log.error("Msg {} serialization failure", text, e);
            }

            throw new IllegalArgumentException();
        }

        return null;
    }

    private void send(ChatKit chatKit, TimMessage timMessage, WsRequest wsRequest, ChannelContext channelContext, int retries, String error) {
        if (retries < 1) {
            var errorMessage = new TimMessage();
            errorMessage.setMsgType(MsgType.FAIURE);
            errorMessage.setContent(error);
            Tio.sendToUser(
                    channelContext.tioConfig,
                    timMessage.getFromUserId(),
                    WsResponse.fromText(gson.toJson(errorMessage), "utf-8"));
            return;
        }

        chatKit.chat(
                wsRequest,
                timMessage,
                channelContext,
                workPool.next().<TimMessage>newPromise().addListener(future -> {

                    if (future.isSuccess()) {
                        if (log.isDebugEnabled()) {
                            log.debug("Send msg {} success", timMessage);
                        }

                        var message = (TimMessage) future.getNow();
                        if (MsgType.READ.equals(message.getMsgType())) {
                            return;
                        }

                        var fromUserId = message.getFromUserId();
                        chatKit.chat(
                                wsRequest,
                                message
                                        .setMsgType(MsgType.ACK)
                                        .setToUserId(message.getFromUserId())
                                        .setFromUserId(fromUserId),
                                channelContext,
                                workPool.next().<TimMessage>newPromise().addListener(future1 -> {
                                    if (future1.isSuccess()) {
                                        if (log.isDebugEnabled()) {
                                            log.debug("Send ack msg {} success", message);
                                        }
                                    } else {
                                        if (log.isErrorEnabled()) {
                                            log.error("Send ack msg {} fail", future1.cause());
                                        }

                                        send(chatKit, timMessage, wsRequest, channelContext, retries - 1, future1.cause().getMessage());
                                    }
                                }));
                    } else {
                        if (log.isErrorEnabled()) {
                            log.error("Send msg {} fail, retries {}", future.cause(), retries);
                        }

                        send(chatKit, timMessage, wsRequest, channelContext, retries - 1, future.cause().getMessage());
                    }
                }));
    }

    private Collection<ChatKit> getChatKits() {
        return applicationContext.getBeansOfType(ChatKit.class).values();
    }

    @Override
    public void onAfterHandshaked(HttpRequest httpRequest, HttpResponse httpResponse, ChannelContext channelContext) throws Exception {
    }

    @Override
    public Object onBytes(WsRequest wsRequest, byte[] bytes, ChannelContext channelContext) throws Exception {
        return null;
    }
}
